package com.vivi.codereview.tools;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import com.vivi.codereview.WhatTheFuckException;
import com.vivi.codereview.actors.BaseActor;

public class UserInputHandler {

  private static final String INPUT_REGEXP_PREFIX = "+";

  private SelectRangeHandler selectRangeHandler;

  public UserInputHandler() {
    this.selectRangeHandler = new SelectRangeHandler();
  }

  /**
   * Display list of choices for the user, with an idnex for each entry. A Map
   * is returned with the index as key, the refered object as value
   *
   * @param items
   * @return
   */
  private <E> Map<Integer, E> displayChoice(Set<E> items) throws Exception {
    // We use a Map because Set is not a List and has not .get(int) method.
    // We generate Map each time, in case some data have been updated
    // meanwhile.
    Map<Integer, E> inputMap = new HashMap<Integer, E>();
    int index = 1;
    for (E item : items) {
      Log.log("[" + index + "]\t" + item.toString(), Log.I);
      inputMap.put(index, item);
      index++;
    }
    return inputMap;
  }

  /**
   * Get the String entered by the user. Any IO exception will stop the
   * program
   *
   * @param string
   * @return
   */
  public String getUserInput(String string) throws Exception {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in), BaseActor.BUFFER_SYSTEM_IN);
    String result = null;
    Log.log(string, Log.I);
    try {
      result = br.readLine();
      Log.log("User entered [" + result + "]", Log.D);
    }
    catch (IOException e) {
      Log.log("Issue while reading keyboard " + e.getMessage(), Log.W);
      throw new WhatTheFuckException(Log.Exit.IO_EXCEPTION);
    }
    return result;
  }

  /**
   * When user has to select one item in a list, we display all the list, with
   * an index for each item and ask the user to enter the index we wants. We
   * are looping 5 times before raising exception in case user is entering
   * invalid data. This exception will stop the flow.
   *
   * @param items
   * @param label
   * @param allowRegexp
   *          do we allow regexp to find the right folder?
   * @return
   */
  public String letUserChooseOne(Set<String> items, String label, boolean allowRegexp) throws Exception {
    int remaining = BaseActor.MAX_NB_ATTEMPTS_TO_ENTER_VALUE;
    while (remaining > 0) {
      Log.logSeparator();
      Map<Integer, String> inputMap = displayChoice(items);
      String fullLabel = label;
      if (allowRegexp) {
        fullLabel += "\nYou can also use regexp by adding '+' before the regexp (for example +5412874).";
      }
      String input = getUserInput(fullLabel);
      if (getSelectRangeHandler().isNullSelectRange(input)) {
        Log.log("Since user entered nothing, we return null", Log.D);
        return null;
      }
      else {
        if (allowRegexp && input.startsWith(INPUT_REGEXP_PREFIX)) {
          String selected = getSelectionByRegexp(items, input.substring(1));
          if (selected != null) {
            Log.log("we select " + selected.toString(), Log.D);
            return selected;
          }
          else {
            Log.log("Regexp " + input + " has not corresponding entry.", Log.D);
          }
        }
        else {
          try {
            Integer intInput = Integer.parseInt(input);
            Log.log("int value is " + intInput, Log.D);
            if (inputMap.containsKey(intInput)) {
              String selected = inputMap.get(intInput);
              Log.log("we select " + selected.toString(), Log.D);
              return selected;
            }
            else {
              Log.log("Index " + input + " is not valid. Please try again", Log.W);
            }
          }
          catch (NumberFormatException nfe) {
            Log.log(input + "is not a valid int number, try again.", Log.W);
          }
        }
      }
      remaining--;
    }
    Log.log("Too many errors, we stop the program.", Log.E);
    throw new WhatTheFuckException(Log.Exit.TOO_MANY_ATTEMPTS_FOR_USER_ENTRY);
  }

  private String getSelectionByRegexp(Set<String> items, String input) throws Exception {
    // We are in the case the user entered a regexp, we try to find the right path
    Set<String> correspondingEntries = new TreeSet<String>();
    for (String item : items) {
      if (item.matches(".*"+input+".*")) {
        correspondingEntries.add(item);
      }
    }
    // We now have list of corresponding entries
    if (correspondingEntries.size() == 0) {
      Log.log("No corresponding entry.", Log.W);
      return null;
    }
    else if (correspondingEntries.size() == 1) {
      String reviewFolder = correspondingEntries.iterator().next();
      Log.log("We found this review folder " + reviewFolder, Log.I);
      return reviewFolder;
    }
    else {
      Log.log("We found "+correspondingEntries.size()+" entries corresponding to " + input + ": " + correspondingEntries.toString(), Log.D);
      return letUserChooseOne(correspondingEntries, "Please select the review project you want to use. <Enter> to come back to full list", false);
    }
  }

  /**
   * When user can select one or more items in a list, we display all the
   * list, with an index for each item and ask the user to enter the index we
   * wants. We are looping 5 times before raising exception in case user is
   * entering invalid data. This exception will stop the flow. * is a joker to
   * select everything
   *
   * @param items
   * @param label
   * @return
   */
  public <E> Set<E> letUserChooseMany(Set<E> items, Comparator<E> c, String label) throws Exception {
    int remaining = BaseActor.MAX_NB_ATTEMPTS_TO_ENTER_VALUE;
    while (remaining > 0) {
      Log.logSeparator();
      Map<Integer, E> inputMap = displayChoice(items);
      String input = getUserInput(label);
      if (getSelectRangeHandler().isNullSelectRange(input)) {
        Log.log("Since user entered nothing, we return empty Set", Log.D);
        return new TreeSet<E>(c);
      }
      else if (getSelectRangeHandler().isAllSelectRange(input)) {
        Log.log("Since user entered joker, we return the origin list " + items, Log.D);
        return items;
      }
      else if (getSelectRangeHandler().isValidForSelectRange(input)) {
        Set<E> selected = new TreeSet<E>(c);
        Set<Integer> inputs = getSelectRangeHandler().fromSelectRangeToExplodedSelection(input);
        for (Integer indexCheck : inputs) {
          if (inputMap.containsKey(indexCheck)) {
            E selectedItem = inputMap.get(indexCheck);
            Log.log("We add " + selectedItem.toString(), Log.D);
            selected.add(selectedItem);
          }
          else {
            Log.log("this value does not exist " + indexCheck, Log.W);
          }
        }
        Log.log("We selected " + selected.toString(), Log.D);
        return selected;
      }
      else {
        Log.log(input + " is not valid. " + getSelectRangeHandler().getMessageWrongSelectRange(), Log.W);
        remaining--;
      }
    }
    Log.log("Please, have a look at what you are entering", Log.E);
    throw new WhatTheFuckException(Log.Exit.TOO_MANY_ATTEMPTS_FOR_USER_ENTRY);
  }

  /**
   * @return the selectRangeHandler
   */
  protected SelectRangeHandler getSelectRangeHandler() {
    return selectRangeHandler;
  }

}
